// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    internal class ModelEntry : IModelEntry
    {
        private class ModelEntryItem
        {
            public object CurrentValue { get; internal set; }
            public object OriginalValue { get; internal set; }
            public PropertyState PropertyState { get; internal set; }
        }

        private readonly Dictionary<string, ModelEntryItem> _entryMap;

        public ModelEntry()
        {
            _entryMap =
                new Dictionary<string, ModelEntryItem>(
                    StringComparer.OrdinalIgnoreCase);
        }

        public ModelEntry(ModelState modelState) : this()
        {
            this.ModelState = modelState;
        }

        /// <summary>
        ///     Occurs when the current value is changed.
        /// </summary>
        public event Action<string, object> CurrentValueChanged;

        internal Type ModelType { get; private set; }

        /// <summary>
        ///     Gets the tracked state of the data.
        /// </summary>
        public ModelState ModelState { get; set; }

        /// <summary>
        ///     Gets the property names of the model class.
        /// </summary>
        public string[] Properties => _entryMap.Keys.ToArray();

        public void SetPropertyEntry(
            string name,
            object currentValue,
            object originalValue,
            PropertyState propertyState = PropertyState.NotModified)
        {
            var item = new ModelEntryItem()
            {
                CurrentValue = currentValue,
                OriginalValue = originalValue,
                PropertyState = propertyState
            };

            if (!_entryMap.ContainsKey(name))
            {
                _entryMap.Add(name, item);
            }
            else
            {
                _entryMap[name] = item;
            }
        }

        /// <summary>
        ///     Gets the current value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The current value of the specified property.</returns>
        public object GetCurrentValue(string name)
        {
            if (_entryMap.TryGetValue(name, out var item))
            {
                return item.CurrentValue;
            }

            return null;
        }

        /// <summary>
        ///     Gets the original value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The original value of the specified property.</returns>
        public object GetOriginalValue(string name)
        {
            if (_entryMap.TryGetValue(name, out var item))
            {
                if (item.PropertyState == PropertyState.Modified)
                {
                    return item.OriginalValue;
                }
                else
                {
                    return item.CurrentValue;
                }
            }

            return null;
        }

        /// <summary>
        ///     Gets the tracked state of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The tracked state of the specified property.</returns>
        public PropertyState GetPropertyState(string name)
        {
            if (_entryMap.TryGetValue(name, out var item))
            {
                return item.PropertyState;
            }

            return PropertyState.NotModified;
        }

        /// <summary>
        ///     Sets the current value for the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value set to the specified property.</param>
        public void SetCurrentValue(string name, object value)
        {
            if (_entryMap.TryGetValue(name, out var item))
            {
                item.CurrentValue = value;

                item.PropertyState = PropertyState.Modified;

                if (this.ModelState == ModelState.New)
                {
                    this.ModelState = ModelState.NewModified;
                }
                else if (this.ModelState == ModelState.NotModified)
                {
                    this.ModelState = ModelState.Modified;
                }
            }

            this.CurrentValueChanged?.Invoke(name, value);
        }
    }

    internal class ModelEntry<TModel> : ModelEntry, IModelEntry<TModel>
    {
        public ModelEntry(ModelState modelState) : base(modelState)
        {
        }
    }

    internal class ModelEntryComb<TModel> : IModelEntry<TModel>
    {
        private readonly IModelEntry _entry;
        public ModelEntryComb(IModelEntry entry)
        {
            _entry = entry;
        }

        /// <summary>
        ///     Gets the tracked state of the data.
        /// </summary>
        public ModelState ModelState => _entry.ModelState;

        /// <summary>
        ///     Gets the property names of the model class.
        /// </summary>
        public string[] Properties => _entry.Properties;

        /// <summary>
        ///     Occurs when the current value is changed.
        /// </summary>
        public event Action<string, object> CurrentValueChanged
        {
            add => _entry.CurrentValueChanged += value;
            remove => _entry.CurrentValueChanged -= value;
        }

        /// <summary>
        ///     Gets the current value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The current value of the specified property.</returns>
        public object GetCurrentValue(string name)
        {
            return _entry.GetCurrentValue(name);
        }

        /// <summary>
        ///     Gets the original value of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The original value of the specified property.</returns>
        public object GetOriginalValue(string name)
        {
            return _entry.GetOriginalValue(name);
        }

        /// <summary>
        ///     Gets the tracked state of the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The tracked state of the specified property.</returns>
        public PropertyState GetPropertyState(string name)
        {
            return _entry.GetPropertyState(name);
        }

        /// <summary>
        ///     Sets the current value for the specified property.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <param name="value">The value set to the specified property.</param>
        public void SetCurrentValue(string name, object value)
        {
            _entry.SetCurrentValue(name, value);
        }
    }
}
